﻿Imports System.IO
Imports Microsoft.Xna.Framework
Imports Microsoft.Xna.Framework.Content
Imports Microsoft.Xna.Framework.Graphics
Imports Microsoft.Xna.Framework.GamerServices
Imports Microsoft.Xna.Framework.Input
Imports System.Collections.Generic
Imports System

Namespace Processors

    Public Class Terrain

        Public Structure VertexPositionNormalTexture
            Public Position As Vector3
            Public Color As Color
            Public Normal As Vector3
            Public Shared SizeInBytes = 7 * 4
            Public Shared VertexDeclaration As VertexDeclaration = New VertexDeclaration _
            (
                New VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
                New VertexElement(4 * 3, VertexElementFormat.Color, VertexElementUsage.Color, 0),
                New VertexElement(4 * 3 + 4, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0)
                )
        End Structure

        Public Structure VertexMultiTextured
            Public Position As Vector3
            Public Normal As Vector3
            Public TextureCoordinate As Vector4
            Public TextureWeights As Vector4
            Public Shared SizeInBytes As Integer = (3 + 3 + 4 + 4) * 4
            Public Shared VertexElements As VertexElement() = New VertexElement() _
            {
                New VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
                New VertexElement(12, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0),
                New VertexElement(24, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 0),
                New VertexElement(40, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 1)
            }
            Public Shared VertexDeclaration As VertexDeclaration
        End Structure

        Public Structure WaterData
            Public Position As Vector3
            Public Width As Integer
            Public Depth As Integer
            Public Height As Integer
            Public WaterBumpMap As Texture2D
            Public ReflectionMap As Texture2D
            Public RefractionMap As Texture2D
            Public Vertices() As VertexPositionTexture
            Public VertexBuffer As VertexBuffer
            Public Indices() As Integer
            Public IndexBuffer As IndexBuffer
        End Structure

        Public HeightMapTexture As Texture2D
        Public SandTexture As Texture2D
        Public GrassTexture As Texture2D
        Public RockTexture As Texture2D
        Public SnowTexture As Texture2D
        Public TerrainScale As Single
        Public Water() As WaterData
        Public InitX As Integer
        Public InitY As Integer
        Public InitZ As Integer
        Public LightDirection As Vector3
        Public LightAmbience As Single

        Private G As GraphicsDeviceManager
        Private TerrainHeight As Integer
        Private TerrainWidth As Integer
        Private GD As GraphicsDevice
        Private Vertices() As VertexPositionColor
        Private Indices() As Integer
        Private TerrainVertices() As VertexMultiTextured
        Private TerrainVertexBuffer As VertexBuffer
        Private TerrainIndexBuffer As IndexBuffer
        Private TerrainVertexDeclaration As VertexDeclaration
        Private WaterVertexDeclaration As VertexDeclaration


        Public Sub New(Graphics As GraphicsDeviceManager)
            G = Graphics
            GD = Graphics.GraphicsDevice
        End Sub

        Public Sub BuildTerrain()
            Dim VE() As VertexElement
            Dim i As Integer
            TerrainHeight = HeightMapTexture.Height
            TerrainWidth = HeightMapTexture.Width
            VE = VertexPositionNormalTexture.VertexDeclaration.GetVertexElements
            Call CreateVertices()
            Call CreateIndices()
            TerrainVertices = CalculateNormals(TerrainVertices, Indices)
            CopyToTerrainBuffers(TerrainVertices, Indices)
            TerrainVertexDeclaration = New VertexDeclaration(VE)
            If Water Is Nothing Then
            Else
                For i = 0 To Water.Length - 1
                    SetUpWaterVertices(Water(i))
                Next i
            End If
        End Sub

        Private Function CalculateNormals(Vertices() As VertexMultiTextured, Indices() As Integer) As VertexMultiTextured()
            Dim i As Integer
            Dim index1 As Integer
            Dim index2 As Integer
            Dim index3 As Integer
            Dim side1 As Vector3
            Dim side2 As Vector3
            Dim normal As Vector3
            For i = 0 To Vertices.Length - 1
                Vertices(i).Normal = New Vector3(0, 0, 0)
            Next i
            For i = 0 To (Indices.Length / 3) - 1
                index1 = Indices(i * 3)
                index2 = Indices(i * 3 + 1)
                index3 = Indices(i * 3 + 2)
                side1 = Vertices(index1).Position - Vertices(index3).Position
                side2 = Vertices(index1).Position - Vertices(index2).Position
                normal = Vector3.Cross(side1, side2)
                Vertices(index1).Normal = Vertices(index1).Normal + normal
                Vertices(index2).Normal = Vertices(index2).Normal + normal
                Vertices(index3).Normal = Vertices(index3).Normal + normal
            Next i
            For i = 0 To Vertices.Length - 1
                Vertices(i).Normal.Normalize()
            Next i
            Return Vertices
        End Function

        Public Sub SetUpWaterVertices(ByRef Water As WaterData)
            Dim WaterVerts As VertexPositionTexture() = New VertexPositionTexture(4) {}
            Dim WaterIndices(5) As Integer
            Dim WaterVertexBuffer As VertexBuffer
            Dim WaterIndexBuffer As IndexBuffer
            WaterVerts(0) = New VertexPositionTexture(Water.Position, New Vector2(0, 0))
            WaterVerts(1) = New VertexPositionTexture(New Vector3(Water.Position.X + Water.Width, Water.Position.Y, Water.Position.Z), New Vector2(1, 0))
            WaterVerts(2) = New VertexPositionTexture(New Vector3(Water.Position.X, Water.Position.Y, Water.Position.Z + Water.Depth), New Vector2(0, 1))
            WaterVerts(3) = New VertexPositionTexture(New Vector3(Water.Position.X + Water.Width, Water.Position.Y, Water.Position.Z + Water.Depth), New Vector2(1, 1))
            Water.Vertices = WaterVerts
            WaterIndices(0) = 0
            WaterIndices(1) = 1
            WaterIndices(2) = 3
            WaterIndices(3) = 0
            WaterIndices(4) = 3
            WaterIndices(5) = 2
            Water.Indices = WaterIndices
            WaterVertexDeclaration = VertexPositionTexture.VertexDeclaration
            WaterVertexBuffer = New VertexBuffer(G.GraphicsDevice, VertexPositionTexture.VertexDeclaration, WaterVerts.Length, BufferUsage.WriteOnly)
            WaterVertexBuffer.SetData(Of VertexPositionTexture)(WaterVerts)
            Water.VertexBuffer = WaterVertexBuffer
            WaterIndexBuffer = New IndexBuffer(G.GraphicsDevice, IndexElementSize.ThirtyTwoBits, WaterIndices.Length, BufferUsage.WriteOnly)
            WaterIndexBuffer.SetData(WaterIndices)
            Water.IndexBuffer = WaterIndexBuffer
        End Sub

        Private Sub CopyToTerrainBuffers(Vertices() As VertexMultiTextured, Indices() As Integer)
            TerrainVertexBuffer = New VertexBuffer(G.GraphicsDevice, VertexMultiTextured.VertexDeclaration, Vertices.Length, BufferUsage.WriteOnly)
            TerrainVertexBuffer.SetData(Of VertexMultiTextured)(Vertices)
            TerrainIndexBuffer = New IndexBuffer(G.GraphicsDevice, IndexElementSize.ThirtyTwoBits, Indices.Length, BufferUsage.WriteOnly)
            TerrainIndexBuffer.SetData(Indices)
        End Sub

        Private Sub CreateVertices()
            Dim x As Integer, y As Integer, total As Single
            Dim MinHeight As Single
            Dim MaxHeight As Single
            Dim HeightSpan As Single
            Dim SandHeight As Single
            Dim GrassHeight As Single
            Dim RockHeight As Single
            ReDim Vertices(TerrainWidth * TerrainHeight)
            Dim HMColors As Color() = New Color((TerrainWidth * TerrainHeight) - 1) {}
            HeightMapTexture.GetData(Of Color)(HMColors)
            Dim HeightData(TerrainWidth, TerrainHeight) As Single
            For x = 0 To TerrainWidth - 1
                For y = 0 To TerrainHeight - 1
                    HeightData(x, y) = HMColors(x + (y * TerrainWidth)).R '* TerrainScale
                    If HeightData(x, y) > MaxHeight Then
                        MaxHeight = HeightData(x, y)
                    End If
                    If HeightData(x, y) < MinHeight Then
                        MinHeight = HeightData(x, y)
                    End If
                Next y
            Next x

            For x = 0 To TerrainWidth - 1
                For y = 0 To TerrainHeight - 1
                    HeightData(x, y) = (HeightData(x, y) - MinHeight) / (MaxHeight - MinHeight) * 30
                Next y
            Next x
            HeightSpan = MaxHeight - MinHeight
            SandHeight = (HeightSpan * 0.25) + MinHeight
            GrassHeight = (HeightSpan * 0.25) + SandHeight
            RockHeight = (HeightSpan * 0.25) + GrassHeight

            Dim TV As VertexMultiTextured() = New VertexMultiTextured((TerrainWidth * TerrainHeight)) {}
            For x = 0 To TerrainWidth - 1
                For y = 0 To TerrainHeight - 1
                    total = 0
                    TV(x + y * TerrainWidth).Position = New Vector3(x, HeightData(x, y), -y)
                    TV(x + y * TerrainWidth).TextureCoordinate.X = x / 10
                    TV(x + y * TerrainWidth).TextureCoordinate.Y = y / 10

                    TV(x + y * TerrainWidth).TextureWeights.X = MathHelper.Clamp(1 - Math.Abs(HeightData(x, y) - 0) / 8, 0, 1)
                    TV(x + y * TerrainWidth).TextureWeights.Y = MathHelper.Clamp(1 - Math.Abs(HeightData(x, y) - 12) / 6, 0, 1)
                    TV(x + y * TerrainWidth).TextureWeights.Z = MathHelper.Clamp(1 - Math.Abs(HeightData(x, y) - 20) / 6, 0, 1)
                    TV(x + y * TerrainWidth).TextureWeights.W = MathHelper.Clamp(1 - Math.Abs(HeightData(x, y) - 30) / 6, 0, 1)
                    total = TV(x + y * TerrainWidth).TextureWeights.X
                    total = total + TV(x + y * TerrainWidth).TextureWeights.Y
                    total = total + TV(x + y * TerrainWidth).TextureWeights.Z
                    total = total + TV(x + y * TerrainWidth).TextureWeights.W
                    TV(x + y * TerrainWidth).TextureWeights.X = TV(x + y * TerrainWidth).TextureWeights.X / total
                    TV(x + y * TerrainWidth).TextureWeights.Y = TV(x + y * TerrainWidth).TextureWeights.Y / total
                    TV(x + y * TerrainWidth).TextureWeights.Z = TV(x + y * TerrainWidth).TextureWeights.Z / total
                    TV(x + y * TerrainWidth).TextureWeights.W = TV(x + y * TerrainWidth).TextureWeights.W / total
                    VertexMultiTextured.VertexDeclaration = New VertexDeclaration(VertexMultiTextured.VertexElements)
                Next y
            Next x
            TerrainVertices = TV
        End Sub

        Private Sub CreateIndices()
            Dim x As Integer, y As Integer
            Dim counter As Integer
            Dim TopLeft As Integer
            Dim TopRight As Integer
            Dim BottomLeft As Integer
            Dim BottomRight As Integer
            ReDim Indices((TerrainWidth - 1) * (TerrainHeight - 1) * 6)
            counter = -1
            For y = 0 To TerrainHeight - 2
                For x = 0 To TerrainWidth - 2
                    TopLeft = CInt(x + y * TerrainWidth)
                    TopRight = CInt((x + 1) + (y * TerrainWidth))
                    BottomRight = CInt((x + 1) + ((y + 1) * TerrainWidth))
                    BottomLeft = CInt(x + ((y + 1) * TerrainWidth))
                    counter = counter + 1
                    Indices(counter) = TopLeft
                    counter = counter + 1
                    Indices(counter) = BottomRight
                    counter = counter + 1
                    Indices(counter) = BottomLeft
                    counter = counter + 1
                    Indices(counter) = TopLeft
                    counter = counter + 1
                    Indices(counter) = TopRight
                    counter = counter + 1
                    Indices(counter) = BottomRight
                Next x
            Next y
        End Sub

        Private Sub DrawWater(Effect As Effect, Camera As Cameras.FirstPerson)
            Dim i As Integer
            Dim RE As Effect
            Dim M As Matrix
            RE = Effect.Clone
            If Water Is Nothing Then
                M = Camera.GetViewMatrix
                Effect.Parameters("xView").SetValue(M)
            Else
                M = Camera.GetReflectionViewMatrix(0)
                Effect.Parameters("xView").SetValue(M)
                For i = 0 To Water.Length - 1
                    DrawReflectionMap(Effect, Camera, Water(i))
                    DrawRefractionMap2(Effect, Camera, Water(i))
                    RE.CurrentTechnique = RE.Techniques("Water")
                    RE.Parameters("xReflectionMap").SetValue(SnowTexture)
                    RE.Parameters("xRefractionMap").SetValue(SnowTexture)

                    For Each Pass As EffectPass In RE.CurrentTechnique.Passes
                        'GD.SetVertexBuffer(Water(0).VertexBuffer)
                        'GD.DrawPrimitives(PrimitiveType.TriangleList, 0, Water(i).Vertices.Length / 3)
                        Pass.Apply()
                    Next
                    GD.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, Water(i).Vertices, 0, Water(i).Vertices.Length, Water(i).Indices, 0, Water(0).Indices.Length / 3, VertexPositionTexture.VertexDeclaration)
                Next i
            End If
        End Sub

        Public Sub Draw(Effect As Effect, Camera As Cameras.FirstPerson)
            DrawWater(Effect, Camera)
            DrawTerrain(Effect, Camera)
        End Sub

        Private Sub DrawTerrain(Effect As Effect, Camera As Cameras.FirstPerson)
            GD.Clear(Color.Black)

            Effect.CurrentTechnique = Effect.Techniques("MultiTextured")
            Effect.Parameters("xTexture0").SetValue(SandTexture)
            Effect.Parameters("xTexture1").SetValue(GrassTexture)
            Effect.Parameters("xTexture2").SetValue(RockTexture)
            Effect.Parameters("xTexture3").SetValue(SnowTexture)
            Effect.Parameters("xView").SetValue(Camera.GetViewMatrix)
            Effect.Parameters("xProjection").SetValue(Camera.GetProjMatrix)
            Effect.Parameters("xWorld").SetValue(Matrix.Identity)
            Effect.Parameters("xEnableLighting").SetValue(True)
            Effect.Parameters("xAmbient").SetValue(LightAmbience)
            Effect.Parameters("xLightDirection").SetValue(LightDirection)

            For Each Pass As EffectPass In Effect.CurrentTechnique.Passes
                Pass.Apply()
            Next
            GD.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, TerrainVertices, 0, TerrainVertices.Length, Indices, 0, Indices.Length / 3, VertexMultiTextured.VertexDeclaration)

        End Sub

        Private Sub DrawRefractionMap2(Effect As Effect, Camera As Cameras.FirstPerson, Water As WaterData)
            Dim refractionPlane As Plane = CreatePlane(Water.Position.Y + 1.5, New Vector3(0, -1, 0), Camera.GetProjMatrix, Camera.GetViewMatrix, False)
            Dim RefractionRenderTarget As New RenderTarget2D(GD, GD.Viewport.Width, GD.Viewport.Height)
            Dim RefractionMap As Texture2D
            Effect.CurrentTechnique = Effect.Techniques("MultiTextured")
            Effect.Parameters("xClipPlane").SetValue(New Vector4(refractionPlane.Normal, refractionPlane.D))
            Effect.Parameters("xClipping").SetValue(True)
            GD.SetRenderTarget(RefractionRenderTarget)
            GD.Clear(ClearOptions.Target, Color.Black, 1, 0)
            DrawTerrain(Effect, Camera)
            GD.SetRenderTarget(Nothing)
            Effect.Parameters("xClipping").SetValue(False)
            RefractionMap = RefractionRenderTarget
            'Dim s As String = System.AppDomain.CurrentDomain.BaseDirectory & "refractionmap.jpg"
            'Dim FS As New System.IO.FileStream(s, FileMode.OpenOrCreate)
            'RefractionMap.SaveAsJpeg(FS, RefractionMap.Width, RefractionMap.Height)
            'FS.Close()
            'FS = Nothing
        End Sub

        Private Sub DrawRefractionMap(Effect As Effect, Camera As Cameras.FirstPerson, Water As WaterData)
            Dim refractionPlane As Plane = CreatePlane(Water.Position.Y + 1.5, New Vector3(0, -1, 0), Camera.GetProjMatrix, Camera.GetViewMatrix, False)
            Dim RefractionRenderTarget As New RenderTarget2D(GD, GD.Viewport.Width, GD.Viewport.Height)
            Dim RefractionMap As Texture2D
            Effect.CurrentTechnique = Effect.Techniques("MultiTextured")

            Effect.Parameters("xClipPlane").SetValue(New Vector4(refractionPlane.Normal, refractionPlane.D))
            Effect.Parameters("xClipping").SetValue(True)
            For Each Pass As EffectPass In Effect.CurrentTechnique.Passes
                Pass.Apply()
            Next
            GD.SetRenderTarget(RefractionRenderTarget)
            GD.Clear(ClearOptions.Target, Color.Black, 1, 0)
            DrawTerrain(Effect, Camera)
            Effect.Parameters("xClipping").SetValue(False)
            GD.SetRenderTarget(Nothing)
            Dim RT As Color() = New Color((RefractionRenderTarget.Width * RefractionRenderTarget.Height) - 1) {}
            RefractionRenderTarget.GetData(Of Color)(RT)
            RefractionMap = New Texture2D(GD, RefractionRenderTarget.Width, RefractionRenderTarget.Height)
            RefractionMap.SetData(Of Color)(RT)
            Water.RefractionMap = RefractionMap
            'Dim FS As New System.IO.FileStream(System.AppDomain.CurrentDomain.BaseDirectory & "refractionmap.jpg", FileMode.OpenOrCreate)
            'RefractionMap.SaveAsJpeg(FS, RefractionMap.Width, RefractionMap.Height)
            'FS.Close()
            'FS = Nothing
        End Sub

        Private Sub DrawReflectionMap(Effect As Effect, Camera As Cameras.FirstPerson, Water As WaterData)
            Dim refractionPlane As Plane = CreatePlane2(Camera, Water.Position.Y + 1.5, New Vector3(0, -1, 0), Camera.GetReflectionViewMatrix(0), True)
            Dim ReflectionRenderTarget As New RenderTarget2D(GD, GD.Viewport.Width, GD.Viewport.Height)
            Dim ReflectionMap As Texture2D
            Dim RE As Effect
            RE = Effect.Clone
            RE.CurrentTechnique = RE.Techniques("MultiTextured")
            RE.Parameters("xView").SetValue(Camera.GetReflectionViewMatrix(Water.Position.Y + 1.5))
            RE.Parameters("xClipPlane").SetValue(New Vector4(-refractionPlane.Normal, -refractionPlane.D))
            RE.Parameters("xClipping").SetValue(True)
            For Each Pass As EffectPass In RE.CurrentTechnique.Passes
                Pass.Apply()
            Next
            GD.SetRenderTarget(ReflectionRenderTarget)
            GD.Clear(ClearOptions.Target, Color.Black, 1, 0)
            DrawTerrain(RE, Camera)
            RE.Parameters("xClipping").SetValue(False)
            Dim RT As Color() = New Color((ReflectionRenderTarget.Width * ReflectionRenderTarget.Height) - 1) {}
            GD.SetRenderTarget(Nothing)
            ReflectionRenderTarget.GetData(Of Color)(RT)
            ReflectionMap = New Texture2D(GD, ReflectionRenderTarget.Width, ReflectionRenderTarget.Height)
            ReflectionMap.SetData(Of Color)(RT)
            Water.ReflectionMap = ReflectionMap
            'Dim FS As New System.IO.FileStream(System.AppDomain.CurrentDomain.BaseDirectory & "reflectionmap.jpg", FileMode.OpenOrCreate)
            'ReflectionMap.SaveAsJpeg(FS, ReflectionMap.Width, ReflectionMap.Height)
            'FS.Close()
            'FS = Nothing
            RE = Nothing
        End Sub

        Private Function CreatePlane2(Camera As Cameras.FirstPerson, Height As Single, PlaneNormalDirection As Vector3, CurrentViewMatrix As Matrix, ClipSide As Boolean) As Plane
            PlaneNormalDirection.Normalize()
            Dim planeCoeffs As Vector4 = New Vector4(PlaneNormalDirection, Height)
            If ClipSide Then
                planeCoeffs = planeCoeffs * -1
            End If
            'Dim WVProj As Matrix = CurrentViewMatrix * Camera.GetProjMatrix
            'Dim IWVProj As Matrix = Matrix.Invert(WVProj)
            'IWVProj = Matrix.Transpose(IWVProj)
            'planeCoeffs = Vector4.Transform(planeCoeffs, IWVProj)
            Dim finalPlane As Plane = New Plane(planeCoeffs)
            Return finalPlane
        End Function

        Private Function CreatePlane(Height As Single, planeNormalDirection As Vector3, CurrentProjMatrix As Matrix, CurrentViewMatrix As Matrix, ClipSide As Boolean) As Plane
            planeNormalDirection.Normalize()
            Dim planeCoeffs As Vector4 = New Vector4(planeNormalDirection, Height)
            If ClipSide Then
                planeCoeffs = planeCoeffs * -1
            End If
            Dim FinalPlane As Plane = New Plane(planeCoeffs)
            Return FinalPlane
        End Function
    End Class

End Namespace
